/*------------------------------------------------------------------------------
 *
 * Dock.java - Implements a standard docking component.
 * 
 * January 2009, Forhad Ahmed
 * 
 *------------------------------------------------------------------------------
 *     
 * - The internal structure of the dock is mainly a tree of VDockNode's  
 * - The leaves of the tree (VDOCK_LEAF) are the visible subcomponents, and the 
 *   internal nodes of the tree (VDOCK_INODE) are invisible.  
 * - Each INODE tree node is either split horizontally (VDOCK_H) or vertically 
 *   (VDOCK_V) and must have at least 2 children.
 * - Each INODE contains splitters to separate its children and must have at 
 *   least (children - 1) splitters.
 * - If 2 INODE's are parent/child of each other, they must be of opposite 
 *   orientation (i.e. if parent is V then child is H, and vice versa) 
 * 
 * example:
 * 
 *   +---+---+-----+                     [H]
 *   | A | B |  C  |                      |           
 *   |   |   |     |               [V]----+----[V]
 *   +---+-+-+-----+  =======>      |           |
 *   |     |   E   |             +--+--+     +--+--[H]
 *   |  D  +-------+             |  |  |     |      |
 *   |     |   F   |             A  B  C     D    +-+-+
 *   +-----+-------+                              |   |
 *                                                E   F
 *                                            
 *------------------------------------------------------------------------------
 */

package ui.dock;

import java.awt.AlphaComposite;
import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Rectangle;
import java.awt.Stroke;
import java.awt.event.ComponentAdapter;
import java.awt.event.ComponentEvent;
import java.util.Vector;

import javax.swing.JComponent;

import tree.Tree;
import tree.TreeNode;

public class Dock extends JComponent {
    public Dock() {
        this.tree = new Tree();
        this.leaves = new Vector();
        this.preview = new VDockPreviewContext();
        this.setSize(0xFFFF, 0xFFFF);
        this.addComponentListener(new DockComponentAdapter(this));
        objects++;
    }

    public void finalize() {
        objects--;
    }

    public DockNode getRoot() {
        return (DockNode) this.tree.root;
    }

    public void sync() {
        DockNode root = this.getRoot();

        if (root != null) {
            root.syncAll();
        }
    }

    public DockNode addComponent(JComponent c) {
        DockNode node = new DockNode(this, c);
        this.insert(node, (DockNode) this.tree.getRoot(), 0, 0, false);
        return node;
    }
    
    public void resizeTree() {
        int width, height, minW, minH;
        /*
         * Handle a race condition here. This function will be called from the
         * EventDispatch thread when the dock is resized and will do a
         * syncSplitters() operation eventually. While this is happening in the
         * EventDispatch thread, components and splitters can be added to the
         * dock by the main thread (app startup).
         * 
         * So we end up with a situation where the syncSplitters() function
         * might be called from this function while in the main thread the
         * splitters are being added, and the splitters invariants (various
         * asserts regarding splitter/child relationships) might not hold in
         * syncSplitters().
         */
        DockNode root = (DockNode) this.tree.root;
        if (root != null) {
            /*
             * Do the resize based on the minimum height/width of the root node.
             */
            width = getWidth();
            height = getHeight();
            minW = root.hCapacity(true); 
            minH = root.vCapacity(true);
            
            if (width < DockNode.DOCKNODE_MINWIDTHFACTOR * minW) {
                width = DockNode.DOCKNODE_MINWIDTHFACTOR * minW;
            }
            
            if (height < DockNode.DOCKNODE_MINWIDTHFACTOR * minH) {
                height = DockNode.DOCKNODE_MINWIDTHFACTOR * minH;
            }
            
            root.setBoundsLinear2(getX(), getY(), width, height);
            root.syncAll();
        }
    }

    
    public void insert(DockNode s, DockNode d, int i, int pos, boolean asym) {
        DockNode p, parent, dis, root;
        DockNode oldSplitterSrc = null, oldSplitterDst = null;
        DockNode first = null, second = null;
        DockSplitter splitter;
        int idx, srcIdx, splitterIdx, size, dx, dy, dw, dh, hoff = 0, woff = 0;
        boolean middle = false;

        root = (DockNode) tree.getRoot();
        assert (s != null);
        assert (s.component != null);
        assert (s.getType() == DOCK_LEAF);

        if (d == null) {
            /*
             * First time insertion into the tree. We will assume that src does
             * not need any fix-up as src's parent is null;
             */
            assert (s.parent == null);
            assert (this.tree.getRoot() == null);

            s.setBoundsLinear(getX(), getY(), getWidth(), getHeight());
            this.tree.setRoot(s);
            root = (DockNode) tree.getRoot();

        } else {
            if (d.getType() == DOCK_INODE) {
                assert (pos == 0 || pos == 1);
                assert (i < d.children.size());

                size = d.children.size();
                srcIdx = i + pos;
                splitterIdx = srcIdx - 1;

                if (splitterIdx >= 0 && splitterIdx < d.splitters.size()) {
                    splitter = (DockSplitter) d.splitters.get(splitterIdx);
                    oldSplitterSrc = splitter.src;
                    oldSplitterDst = splitter.dst;
                    d.removeSplitter(splitterIdx);
                }

                dis = (DockNode) d.children.get(i);
              
                /*
                 * If asymmetric is true then we keep the offset value or the 
                 * negative of the offset value, based on 'pos'.
                 */
                if (asym) {   
                    woff = (int)(dis.w * (0.5 - 0.4));
                    hoff = (int)(dis.h * (0.5 - 0.4));
                    woff = (pos == 0 ? -woff : woff);
                    hoff = (pos == 0 ? -hoff : hoff);
                }
                
                if (pos == 0) {
                    first = s;
                    second = dis;
                } else if (pos == 1) {
                    first = dis;
                    second = s;
                } else {
                    assert (false);
                }
                
                dx = dis.x; dy = dis.y; dw = dis.w; dh = dis.h;
                if (d.orientation == DOCK_V) {
                    first.setBoundsLinear(dx, dy, dw / 2 + woff, dh);
                    second.setBoundsLinear(dx + first.w, dy, dw - first.w, dh);
                } else if (d.orientation == DOCK_H) {
                    first.setBoundsLinear(dx, dy, dw, dh / 2 + hoff);
                    second.setBoundsLinear(dx, dy + first.h, dw, dh - first.h);
                }

                d.addChildAtIndex(s, srcIdx);

                /*
                 * Add new splitters if necessary
                 */
                if (srcIdx == 0) {
                    splitter = new DockSplitter();
                    d.addSplitter(s, dis, splitter);
                } else if (srcIdx == size) {
                    splitter = new DockSplitter();
                    d.addSplitter(dis, s, splitter);
                } else {
                    splitter = new DockSplitter();
                    d.addSplitter(oldSplitterSrc, s, splitter);
                    splitter = new DockSplitter();
                    d.addSplitter(s, oldSplitterDst, splitter);
                }

            } else if (d.getType() == DOCK_LEAF) {

                p = new DockNode(this, null);

                if ((parent = d.getParent()) != null) {

                    idx = parent.children.indexOf(d);
                    size = parent.children.size();

                    /*
                     * Remove splitters
                     */
                    if (idx == 0) {
                        parent.removeSplitter(0);
                        first = p;
                        middle = false;
                        second = (DockNode) parent.children.get(1);
                    } else if (idx == size - 1) {
                        parent.removeSplitter(idx - 1);
                        first = (DockNode) parent.children.get(size - 2);
                        second = p;
                        middle = false;
                    } else {
                        assert (parent.children.size() > 2);
                        parent.removeSplitter(idx);
                        parent.removeSplitter(idx - 1);
                        first = (DockNode) parent.children.get(idx - 1);
                        second = (DockNode) parent.children.get(idx + 1);
                        middle = true;
                    }

                    /*
                     * Remove dst node and add the newly created node
                     */
                    p.orientation = parent.getInverseOrientation();
                    if ((idx = parent.removeChild(d)) >= 0) {
                        parent.addChildAtIndex(p, idx);
                    }

                    /*
                     * Add new splitters
                     */
                    if (middle) {
                        splitter = new DockSplitter();
                        parent.addSplitter(first, p, splitter);
                        splitter = new DockSplitter();
                        parent.addSplitter(p, second, splitter);
                    } else {
                        splitter = new DockSplitter();
                        parent.addSplitter(first, second, splitter);
                    }
                } else {
                    p.orientation = DOCK_V; /* default orientation */
                    this.tree.setRoot(p);
                }

                p.setBoundsLinear(d.x, d.y, d.w, d.h);

                if (asym) {   
                    woff = (int)(p.w * (0.5 - 0.4));
                    hoff = (int)(p.h * (0.5 - 0.4));
                    woff = (pos == 0 ? -woff : woff);
                    hoff = (pos == 0 ? -hoff : hoff);
                }
                
                if (pos == 0) {
                    first = s;
                    second = d;
                } else if (pos == 1) {
                    first = d;
                    second = s;
                } else {
                    assert (false);
                }

                if (p.orientation == DOCK_V) {
                    first.setBoundsLinear(p.x, p.y, p.w / 2 + woff, p.h);
                    second.setBoundsLinear(p.x + first.w, p.y, 
                                           p.w - first.w, p.h);
                } else if (p.orientation == DOCK_H) {
                    first.setBoundsLinear(p.x, p.y, p.w, p.h / 2 + hoff);
                    second.setBoundsLinear(p.x, p.y + first.h,
                                           p.w, p.h - first.h);
                }

                /*
                 * Add src and dst nodes and splitter between them under newly
                 * created node
                 */
                p.addChild(first);
                p.addChild(second);

                splitter = new DockSplitter();
                p.addSplitter(first, second, splitter);

            } else {
                assert (false);
            }
        }

        this.add(s.component);
        this.leaves.add(s);
        this.sync();
    }

    /*
     * Calling remove() might result in the removal of a node whose parent has 
     * only 2 children.  In that case, the remaining child of the parent becomes
     * the parent itself and any references to that remaining child should be
     * changed to the parent. This function returns true in this case so that 
     * callers of this function will know to change the reference properly.
     */
    public boolean remove(DockNode node, int pos) {
        DockNode child, first = null, second = null, parent = null;
        DockSplitter splitter;
        int index, size;
        int oldW, oldH;
        boolean middle = false, rval = false, collapse = false;

        assert (pos == 0 || pos == 1);
        /*
         * Easy, special case for the root leaf node.
         */
        if (node == tree.root) {
            assert (node.type == Dock.DOCK_LEAF);
            tree.root = null;
            this.repaint();
        } else if (node.type != Dock.DOCK_INODE) {
            /*
             * For now, we will be restricting node removals to only leaf nodes.
             */
            parent = node.getParent();
            assert (parent != null);
            assert (parent.children.size() >= 2);

            index = parent.children.indexOf(node);
            size = parent.children.size();

            if (index == 0) {
                parent.removeSplitter(0);
                first = (DockNode) parent.children.get(1);
            } else if (index == size - 1) {
                parent.removeSplitter(index - 1);
                second = (DockNode) parent.children.get(size - 2);
            } else {
                assert (parent.children.size() > 2);
                parent.removeSplitter(index);
                parent.removeSplitter(index - 1);
                first = (DockNode) parent.children.get(index - 1);
                second = (DockNode) parent.children.get(index + 1);
                middle = true;
            }

            oldW = node.w; oldH = node.h;
            parent.removeChild(node);

            /* 
             * Get the left and right of 'node' in case of 'middle' and then add 
             * splitters between them 
             */
            if (middle) {
                splitter = new DockSplitter();
                parent.addSplitter(first, second, splitter);
            }
            
            /*
             * If either 'middle' or (size > 2) then do bounds adjustment based 
             * on the 'position' value.
             */
            if (middle) {
                if (parent.orientation == Dock.DOCK_H) {
                    if (pos == 0) {
                        first.setBoundsLinear(first.x, first.y,  
                                              first.w, first.h + oldH);
                    } else if (pos == 1) {
                        second.setBoundsLinear(second.x, second.y - oldH,
                                               second.w, second.h + oldH);
                    }
                } else if (parent.orientation == Dock.DOCK_V) {
                    if (pos == 0) {
                        first.setBoundsLinear(first.x, first.y, 
                                              first.w + oldW, first.h);
                    } else if (pos == 1) {
                        second.setBoundsLinear(second.x - oldW, second.y, 
                                               second.w + oldW, second.h);
                    }
                }
            } else if (size > 2) {
                /*
                 * 'pos' value does not come into the picture here since there 
                 * is only one node to resize.
                 */
                if (index == 0) {
                    if (parent.orientation == Dock.DOCK_H) {
                        first.setBoundsLinear(first.x, first.y - oldH, 
                                              first.w, first.h + oldH);
                    } else if (parent.orientation == Dock.DOCK_V) {
                        first.setBoundsLinear(first.x - oldW, first.y, 
                                              first.w + oldW, first.h);
                    }
                } else if (index == size - 1) {
                    if (parent.orientation == Dock.DOCK_H) {
                        second.setBoundsLinear(second.x, second.y,
                                               second.w, second.h + oldH);
                    } else if (parent.orientation == Dock.DOCK_V) {
                        second.setBoundsLinear(second.x, second.y,
                                               second.w + oldW, second.h);
                    }
                } else {
                    assert(false);
                }
            }

            /*
             * If we have a case where the parent originally had two children
             * and now, as a result of the remove, has only one, then we need to
             * collapse the single child of the parent into the parent node
             * itself.
             */
            if (parent.children.size() == 1) {
                child = (DockNode) parent.children.get(0);
                rval = collapse(parent, child);
                collapse = true;
            }

        } else {
            /*
             * Do not allow inode removals
             */
            assert(false);
        }

        /*
         * Last, but not least, remove the physical component from the dock, the 
         * VDockNode from the leaves list, and sync everything
         */
        this.remove(node.component);
        this.leaves.remove(node);
        this.sync();
        return rval;
    }

    public boolean collapse(DockNode parent, DockNode child) {
        DockNode grandparent, grandchild, head = null, tail = null;
        DockNode first = null, last = null, link = null;
        DockSplitter splitter;
        int pindex, index, gpsize, csize;
        boolean rval = false, child_had_inode= false;

        assert (parent.children.get(0) == child);
        assert (parent.splitters.size() == 0);

        grandparent = parent.getParent();

        if (child.type == Dock.DOCK_LEAF) {
            /*
             * In this case, we can either remove the parent from grandparent
             * and add child to grandparent, or we can override the fields in
             * parent so that parent *becomes* child. This is the consume() 
             * operation.
             */
            parent.consume(child);
            rval = true;
        } else if (child.type == Dock.DOCK_INODE) {
            assert (child.children.size() >= 2);
            if (grandparent != null) {
                gpsize = grandparent.children.size();
                pindex = grandparent.children.indexOf(parent);

                /*
                 * Head/tail are two nodes in the grandparent's children list 
                 * right before/after the 'hole' that is created by removing
                 * 'parent' from the grandparent.  We will use these later for
                 * linking with the grandchildren.
                 */
                if (pindex == 0) {
                    grandparent.removeSplitter(0);
                    tail = (DockNode) grandparent.children.get(1);
                } else if (pindex == gpsize - 1) {
                    grandparent.removeSplitter(pindex - 1);
                    head = (DockNode) grandparent.children.get(pindex - 1);
                } else {
                    grandparent.removeSplitter(pindex);
                    grandparent.removeSplitter(pindex - 1);
                    head = (DockNode) grandparent.children.get(pindex - 1);
                    tail = (DockNode) grandparent.children.get(pindex + 1);
                }

                grandparent.removeChild(parent);
                parent.removeChild(child);
                while (child.splitters.size() > 0) {
                    child.removeSplitter(0);
                }
                
                /*
                 * Mark the first and last nodes of the child so that we can 
                 * link them with the head and tail nodes we marked for the
                 * grandparent's children. Linking is done as follows:
                 * 
                 *   [head]-[first]-..................-[last]-[tail]
                 *   
                 * In between first and last nodes are the grandchildren.
                 */
                csize = child.children.size();
                first = (DockNode) child.children.get(0);
                last = (DockNode) child.children.get(csize - 1);

                if (head != null) {
                    splitter = new DockSplitter();
                    grandparent.addSplitter(head, first, splitter);
                }

                index = 0;
                while (child.children.size() > 0) {
                    grandchild = (DockNode) child.children.get(0);         
                    child.removeChild(grandchild);
                    grandparent.addChildAtIndex(grandchild, pindex + index);
                    if (link != null) {
                        splitter = new DockSplitter();
                        grandparent.addSplitter(link, grandchild, splitter);
                    }

                    /*
                     * Do some bounds adjustments for the grandchildren
                     */
                    if (grandparent.orientation == Dock.DOCK_H) {
                        grandchild.setBounds(grandparent.x, grandchild.y,
                                             grandparent.w, grandchild.h);
                    } else if (grandparent.orientation == Dock.DOCK_V) {
                        grandchild.setBounds(grandchild.x, grandparent.y,
                                             grandchild.w, grandparent.h);
                    }

                    link = grandchild;
                    index++;
                }

                if (tail != null) {
                    splitter = new DockSplitter();
                    grandparent.addSplitter(last, tail, splitter);
                }
            } else {
                /*
                 * Parent is removed and child becomes the root
                 */
                assert (this.tree.root == parent);
                parent.removeChild(child);
                this.tree.root = child;
                child.setBounds(parent.getBounds());
                
            }
        } else {
            assert (false);
        }
        return rval;
    }
    
    protected DockNode findComponent(JComponent c) {
        DockNode node, rval = null;
        int index;
        
        for (index = 0; index < this.leaves.size(); index++) {
            node = (DockNode) this.leaves.get(index);
            if (node.component == c) {
                rval = node;
                break;
            }
        }
        
        return rval;
    }
    
    public void close(JComponent c) {
        DockNode node;
        
        node = findComponent(c);
        if (node != null) {
            this.remove(node, 0);
        } else {
            assert(false);
        }
    }
    
    public void drag(JComponent s, int x, int y) {
        DockNode src, dst;
        int region;
        int width, height;
        int cx, cy, cw, ch;
        Rectangle highlight;
        
        /*
         * Based on the dst node at x,y (if any) and the type of that node
         * (allows for single or multiple subcomponents (tab-set)) we determine 
         * the highlight characteristics.
         */
        src = this.findComponent(s);
        dst = this.getLeafAtPoint(x, y);
                
        if (dst != null && dst != src) {
            
            cx = dst.component.getX(); 
            cy = dst.component.getY();
            cw = dst.component.getWidth(); 
            ch = dst.component.getHeight();     
            width  = (int) (dst.w * 0.4F);
            height = (int) (dst.h * 0.4F);
            
            if (dst.mdi == false) {
                /*
                 * Call node region calculation function by passing the coords
                 * relative to the node itself
                 */
                region = dst.getPointRegion(x - dst.x, y - dst.y);
                highlight = this.preview.rectangle;
                
                if (region == DockNode.DOCKNODE_LEFT) {
                    this.preview.paint = true;
                    highlight.setBounds(cx, cy, width, ch);
                } else if (region == DockNode.DOCKNODE_RIGHT) {
                    this.preview.paint = true;
                    highlight.setBounds(cx + cw - width, cy, width, ch);
                } else if (region == DockNode.DOCKNODE_TOP) {
                    this.preview.paint = true;
                    highlight.setBounds(cx, cy, cw, height);
                } else if (region == DockNode.DOCKNODE_BOTTOM) {
                    this.preview.paint = true;
                    highlight.setBounds(cx, cy + ch - height, cw, height);
                }
            } else {
                
                // TODO:
            }
        }
        
        repaint();
    }
    
    public void drop(JComponent s, int x, int y) {
        DockNode src, dst, dparent, sparent, gparent;
        int region, index;
        boolean c1, c2, c3, c4;
        
        src = this.findComponent(s);
        dst = this.getLeafAtPoint(x, y);
        
        /*
         * Drop point could be at a splitter in which case the dst node would be
         * null. Bail.
         */
        if (dst == null) return;
        
        sparent = src.getParent();
        dparent = dst.getParent();
        
        if (dst != null && dst != src && dparent != null) {            
            /*
             * The drop() method depends on the value of the dst node's 'mdi' 
             * value indicating whether the destination for the drop is capable
             * of holding multiple components at once (i.e. the destination is a 
             * tab-set).
             */
            if (dst.mdi == false) {
                region = dst.getPointRegion(x - dst.x, y - dst.y);  
                /*
                 * First take care of the special case if the src and dst are
                 * the only children of the same parent and the src->dst drop 
                 * would result in collapse of src and dst into grandparent.
                 */
                c1 = (sparent == dparent);
                c2 = (dparent.children.size() == 2);
                c3 = (dparent.orientation == Dock.DOCK_H && 
                     (region == DockNode.DOCKNODE_LEFT || 
                     (region == DockNode.DOCKNODE_RIGHT)));
                c4 = (dparent.orientation == Dock.DOCK_V && 
                     (region == DockNode.DOCKNODE_TOP || 
                     (region == DockNode.DOCKNODE_BOTTOM)));
                    
                if (c1 && c2 && (c3 || c4)) {
                    gparent = sparent.getParent();
                    if (gparent != null) {
                        this.remove(src, 0);
                        index = gparent.children.indexOf(dparent);
                        if (c3) {
                            if (region == DockNode.DOCKNODE_LEFT) {
                                this.insert(src, gparent, index, 0, true);
                            } else if(region == DockNode.DOCKNODE_RIGHT) {
                                this.insert(src, gparent, index, 1, true);
                            }
                        } else if (c4) {
                            if (region == DockNode.DOCKNODE_TOP) {
                                this.insert(src, gparent, index, 0, true);
                            } else if(region == DockNode.DOCKNODE_BOTTOM) {
                                this.insert(src, gparent, index, 1, true);
                            }                         
                        }
                    } else {
                        // TODO:
                        System.out.println("TODO");
                    }
                    
                } else if (dparent.orientation == Dock.DOCK_H) { 
                    if (region == DockNode.DOCKNODE_RIGHT) {
                        this.remove(src, 0);
                        this.insert(src, dst, 0, 1, true);
                    } else if (region == DockNode.DOCKNODE_LEFT) {
                        this.remove(src, 0);
                        this.insert(src, dst, 0, 0, true);
                    } else if (region == DockNode.DOCKNODE_TOP) {
                        this.remove(src, 0);
                        if (c1 == false) {
                            /*
                             * If the sparent and dparent *before* remove() were 
                             * different, then there's possibility that dparent 
                             * has changed due to the remove() call. Recalculate 
                             * the dst parent again here to be safe. Same logic 
                             * applies to more cases below (not commented)
                             */
                            dparent = dst.getParent();
                        }
                        index = dparent.children.indexOf(dst);
                        this.insert(src, dparent, index, 0, true);
                    } else if (region == DockNode.DOCKNODE_BOTTOM) {
                        this.remove(src, 0);
                        if (!c1) dparent = dst.getParent();
                        index = dparent.children.indexOf(dst);
                        this.insert(src, dparent, index, 1, true);
                    }
                } else if (dparent.orientation == Dock.DOCK_V) {
                    if (region == DockNode.DOCKNODE_TOP) {
                        this.remove(src, 0);
                        this.insert(src, dst, 0, 0, true);
                    } else if (region == DockNode.DOCKNODE_BOTTOM) {
                        this.remove(src, 0);
                        this.insert(src, dst, 0, 1, true);
                    } else if (region == DockNode.DOCKNODE_LEFT) {
                        this.remove(src, 0);
                        if (!c1) dparent = dst.getParent();
                        index = dparent.children.indexOf(dst);
                        this.insert(src, dparent, index, 0, true);
                    } else if (region == DockNode.DOCKNODE_RIGHT) {
                        this.remove(src, 0);
                        if (!c1) dparent = dst.getParent();
                        index = dparent.children.indexOf(dst);
                        this.insert(src, dparent, index, 1, true);
                    } 
                }
            } else {
                
                // TODO:
            }
        }
        
        this.preview.paint = false;
        this.repaint();
    }
    
    public DockNode getLeafAtPoint(int x, int y) {
        DockNode node, rval = null;
        Rectangle bounds;
        int index;

        for (index = 0; index < this.leaves.size(); index++) {
            node = (DockNode) this.leaves.get(index);
            bounds = node.component.getBounds();
            if (bounds.contains(x, y)) {
                rval = node;
                break;
            }
        }    
        return rval;
    }

    
    private AlphaComposite makeComposite(float alpha) {
        int type = AlphaComposite.SRC_OVER;
        return(AlphaComposite.getInstance(type, alpha));
    }
    
    public void paintDragPreview(Graphics g) {
        Graphics2D g2d;
        Rectangle r;
        
        g2d = (Graphics2D) g;
        
        if (this.preview.paint == false) return; 
        
        r = this.preview.rectangle;
        
        if (this.preview.fill) {
            g2d.setColor(preview.color);     
            g2d.setComposite(makeComposite(0.2F));
            g2d.fillRect(r.x + preview.width/2, r.y + preview.width/2, 
                         r.width - preview.width, r.height - preview.width);
            
            g2d.setComposite(makeComposite(0.7F));
            g2d.drawRect(r.x, r.y, r.width - 1, r.height - 1);
            
        } else {
            g2d.setStroke(preview.stroke);
            g2d.setComposite(makeComposite(0.9F));
            g2d.drawRect(r.x + preview.width/2, r.y + preview.width/2, 
                         r.width - preview.width, r.height - preview.width);
        }
               
        
        this.preview.paint = false;
    }
    
    public void paint(Graphics g) {
        g.setColor(bgColor);
        g.fillRect(0, 0, getWidth(), getHeight());
        super.paint(g);
        paintDragPreview(g);
    }

    /*
     * Enumerations
     */
    public static int DOCK_INODE = TreeNode.TREE_INODE;
    public static int DOCK_LEAF = TreeNode.TREE_LEAF;
    public static int DOCK_H = 0;
    public static int DOCK_V = 1;
    public static int DOCK_U = 2;
    
    /*
     * Private data
     */
    public Tree                tree;
    public Vector              leaves;
    public VDockPreviewContext preview; 
    
    /*
     * Options
     */
    public int     padding = 2;
    public int     splitterWidth = 6;
    public Color   bgColor = new Color(236, 233, 216);
    
    
    
    public static int objects = 0;
}

class VDockPreviewContext {
    public Rectangle rectangle = new Rectangle(0,0,0,0);
    public boolean   paint = false;
    
    /*
     * Options
     */
    public int       width  = 3;
    public Stroke    stroke = new BasicStroke(width);
    public boolean   fill   = true;
    public Color     color  = Color.MAGENTA;
}

class DockComponentAdapter extends ComponentAdapter {
    public DockComponentAdapter(Dock target) {
        this.target = target;
        objects++;
    }

    public void finalize() {
        objects--;
    }

    public void componentResized(ComponentEvent e) {
        target.resizeTree();
    }

    boolean temp = true;
    protected Dock target;
    public static int objects = 0;
}
